<?php

use Illuminate\Database\Capsule\Manager as DB;

class HelibaoController extends CommonController
{
    protected $authExcept = [];
    protected $url_test = 'http://test.trx.helipay.com/trx/quickPayApi/interface.action';
    protected $url_proc = 'http://quickpay.trx.helipay.com/trx/quickPayApi/interface.action';
    protected $customerNo = "C1807611968";
    protected $sign_type = 'MD5WITHRSA';
    protected $publicKey = APP_PATH . '/cert/helipay.cer';
    protected $merchantCert = APP_PATH . '/cert/helibao_rsaKey.pfx';
    protected $aesKey = 'aidQY44RFA4qUFwi';

    protected $bankList = [
        'ICBC'     => '工商银行',
        'ABC'      => '农业银行',
        'BOC'      => '中国银行',
        'CCB'      => '建设银行',
        'CMBCHINA' => '招商银行',
        'POST'     => '邮政储蓄',
        'ECITIC'   => '中信银行',
        'CEB'      => '光大银行',
        'BOCO'     => '交通银行',
        'CIB'      => '兴业银行',
        'CMBC'     => '民生银行',
        'PINGAN'   => '平安银行',
        'CGB'      => '广发银行',
        'BCCB'     => '北京银行',
        'HXB'      => '华夏银行',
        'SPDB'     => '浦发银行',
        'SHB'      => '上海银行',
    ];

    public function init()
    {
        parent::init();
        //$this->aesKey = randStr(16);
    }

    public function generateSn($user_id)
    {
        $rand     = $user_id < 9999 ? mt_rand(100000, 99999999) : mt_rand(100, 99999);
        $order_sn = date('Yhis') . $rand;

        $id = str_pad($user_id, (24 - strlen($order_sn)), '0', STR_PAD_BOTH);

        return $order_sn . $id;
    }

    public function bindcardListAction()
    {
        $user = \app\service\auth::getUser();
        if ($user->status != 'normal') {
            throw new Exception("账号状态异常");
        }
        $data = helibaobindModel::query()
            ->where('member_id', $user->id)
            ->where('bind_id', '<>', '')
            ->get();
        ret(0, '合利宝绑定银行卡', $data);
    }

    public function bindcardsmsAction()
    {
        $user = \app\service\auth::getUser();
        if ($user->status != 'normal') {
            throw new Exception("账号状态异常");
        }
        $input = $this->postData;
        $roles = [
            'payer_name' => ['role' => "required", 'msg' => '持卡人姓名'],
            'idcard_no'  => ['role' => "required|len:18", 'msg' => '持卡人身份证号'],
            'card_no'    => ['role' => "required|minlen:16|maxlen:19", 'msg' => '银行卡号'],
            'phone'      => ['role' => "required|len:11", 'msg' => '银行卡预留手机号'],
        ];
        Validate::check($input, $roles);
        $data = helibaobindModel::query()
            ->where('member_id', $user->id)
            ->where('payer_name', $input['payer_name'])
            ->where('idcard_no', $input['idcard_no'])
            ->where('card_no', $input['card_no'])
            ->where('phone', $input['phone'])
            ->first();
        if($data){
            $this->BindCardSendValidateCode($data);
        }else{
            $this->QuickPayBindCardPreOrder($user, $input);
        }

        ret(0, '短信发送成功', $data);

    }

    // 鉴权绑卡预下单接口
    private function QuickPayBindCardPreOrder($user, $input)
    {
//        $input= [
//            'user_id'       => '1000001',
//            'payer_name'    => '刘勇',
//            'idcard_no'     => '411330198307293417',
//            'card_no'       => '6227002432150385025',
//            'year'          => '',
//            'month'         => '',
//            'cvv2'          => '',
//            'phone'         => '13603458837',
//        ];
//
//        $user = \app\service\auth::getUser();
//        if ($user->status != 'normal') {
//            throw new Exception("账号状态异常");
//        }
//        $input = $this->postData;
//        $roles = [
//            'payer_name' => ['role' => "required", 'msg' => '持卡人姓名'],
//            'idcard_no'  => ['role' => "required|len:18", 'msg' => '持卡人身份证号'],
//            'card_no'    => ['role' => "required|minlen:16|maxlen:19", 'msg' => '银行卡号'],
//            'phone'      => ['role' => "required|len:11", 'msg' => '银行卡预留手机号'],
//        ];
//        Validate::check($input, $roles);

        $params = [
            'P1_bizType'        => 'QuickPayBindCardPreOrder',
            'P2_customerNumber' => $this->customerNo,
            'P3_userId'         => $user->id,
            'P4_orderId'        => $this->generateSn($user->id),
            'P5_timestamp'      => date('YmdHis'),
            'P6_payerName'      => $input['payer_name'],
            'P7_idCardType'     => 'IDCARD',
            'P8_idCardNo'       => $this->aes($input['idcard_no']),
            'P9_cardNo'         => $this->aes($input['card_no']),
            'P10_year'          => $this->aes($input['year']),
            'P11_month'         => $this->aes($input['month']),
            'P12_cvv2'          => $this->aes($input['cvv2']),
            'P13_phone'         => $this->aes($input['phone']),
        ];

        $signStr = '&' . implode('&', $params);
        Log::out('bind card preorder $signStr:' . $signStr, 'helibao');

        $params['sign'] = $this->MD5RSA_openssl($signStr);
        //$this->isValid($signStr, $this->MD5RSA_openssl($signStr));

        $params['sendValidateCode'] = 'TRUE';
        $params['protocolType']     = 'protocol';
        $params['signatureType']    = 'MD5WITHRSA';
        $params['encryptionKey']    = $this->encryptionKey();

        Log::out('bind card preorder $params:' . json_encode($params), 'helibao');
        $rows = [
            'member_id'     => $params['P3_userId'],
            'payer_name'    => $input['payer_name'],
            'idcard_no'     => $input['idcard_no'],
            'card_no'       => $input['card_no'],
            'year'          => $input['year'],
            'month'         => $input['month'],
            'cvv2'          => $input['cvv2'],
            'phone'         => $input['phone'],
            'bind_order_no' => $params['P4_orderId'],
            'created_at'    => date('Y-m-d H:i:s')
        ];
        helibaobindModel::insert($rows);

        $resStr = request($this->url_proc, $params);
        Log::out('bind card preorder $resStr:' . $resStr, 'helibao');
        $resArr = json_decode($resStr, true);
        if ($resArr['rt2_retCode'] === '0000') {
            return true;
        } else {
            ret(30001, $resArr['rt3_retMsg']);
        }
    }

    // 绑卡短信
    private function BindCardSendValidateCode($bindData)
    {
//        if (helibaobindModel::query()->where('id', $this->get('id'))->doesntExist()) {
//            throw new Exception("鉴权绑卡预下单数据不存在");
//        }
//        $bindData = helibaobindModel::query()->find($this->get('id'));

        $params = [
            'P1_bizType'        => 'BindCardSendValidateCode',
            'P2_customerNumber' => $this->customerNo,
            'P3_orderId'        => $bindData->bind_order_no,
            'P4_timestamp'      => date('YmdHis'),
        ];

        $signStr = '&' . implode('&', $params);
        Log::out('bind card $signStr:' . $signStr, 'helibao');

        $params['sign'] = $this->MD5RSA_openssl($signStr);
        //$this->isValid($signStr, $this->MD5RSA_openssl($signStr));
        $params['signatureType'] = 'MD5WITHRSA';
        $params['encryptionKey'] = $this->encryptionKey();

        Log::out('bind card $params:' . json_encode($params), 'helibao');
        $resStr = request($this->url_proc, $params);
        Log::out('bind card $resStr:' . $resStr, 'helibao');
        $resArr = json_decode($resStr, true);
        if ($resArr['rt2_retCode'] === '0000') {
            return true;
        } else {
            ret(30002, $resArr['rt3_retMsg']);
        }
    }

    // 鉴权绑卡
    public function ConfirmBindCardAction()
    {
        $input = $this->postData;
        $roles = [
            'payer_name' => ['role' => "required", 'msg' => '持卡人姓名'],
            'idcard_no'  => ['role' => "required|len:18", 'msg' => '持卡人身份证号'],
            'card_no'    => ['role' => "required|minlen:16|maxlen:19", 'msg' => '银行卡号'],
            'phone'      => ['role' => "required|len:11", 'msg' => '银行卡预留手机号'],
            'smscode'    => ['role' => "required", 'msg' => '手机号验证码'],
        ];
        Validate::check($input, $roles);

        $user = \app\service\auth::getUser();
        if ($user->status != 'normal') {
            throw new Exception("账号状态异常");
        }

        $bindData = helibaobindModel::query()
            ->where('member_id', $user->id)
            ->where('payer_name', $input['payer_name'])
            ->where('idcard_no', $input['idcard_no'])
            ->where('card_no', $input['card_no'])
            ->where('phone', $input['phone'])
            ->first();
        if(!$bindData){
            throw new Exception("请先获取短信验证码");
        }

        $params = [
            'P1_bizType'        => 'ConfirmBindCard',
            'P2_customerNumber' => $this->customerNo,
            'P3_orderId'        => $bindData->bind_order_no,
            'P4_timestamp'      => date('YmdHis'),
            'P5_validateCode'   => $this->aes($input['smscode']),
        ];

        $signStr = '&' . implode('&', $params);
        Log::out('confirm bind card $signStr:' . $signStr, 'helibao');

        $params['sign'] = $this->MD5RSA_openssl($signStr);
        //$this->isValid($signStr, $this->MD5RSA_openssl($signStr));
        $params['signatureType'] = 'MD5WITHRSA';
        $params['encryptionKey'] = $this->encryptionKey();

        Log::out('confirm bind card $params:' . json_encode($params), 'helibao');
        $resStr = request($this->url_proc, $params);
        Log::out('confirm bind card $resStr:' . $resStr, 'helibao');

        $resArr = json_decode($resStr, true);
        if ($resArr['rt2_retCode'] === '0000' && $resArr['rt7_bindStatus'] === 'SUCCESS') {
            $bindData->bank    = $this->bankList[$resArr['rt8_bankId']];
            $bindData->bind_id = $resArr['rt10_bindId'];
            $bindData->save();
            ret(0, '绑卡成功');
        } else {
            ret(30003, $resArr['rt3_retMsg']);
        }
    }

    // 绑卡支付预下单
    public function QuickPayBindPayPreOrderAction()
    {
        $user = \app\service\auth::getUser();
        if ($user->status != 'normal') {
            throw new Exception("账号状态异常");
        }
        $input = $this->postData;

        $bindData = helibaobindModel::query()->where('bind_id', $input['bind_id'])->first();
        if (!$bindData) {
            throw new Exception("绑卡数据不存在");
        }
        $order = orderModel::query()->where('order_sn', $input['order_sn'])->first();
        if (!$order) {
            throw new Exception("订单数据不存在");
        }

        $params  = [
            'P1_bizType'            => 'QuickPayBindPayPreOrder',
            'P2_customerNumber'     => $this->customerNo,
            'P3_bindId'             => $bindData->bind_id,
            'P4_userId'             => $user->id,
            'P5_orderId'            => $this->generateSn($user->id),
            'P6_timestamp'          => date('YmdHis'),
            'P7_currency'           => 'CNY',
            'P8_orderAmount'        => $order->pay_fee,
            'P9_goodsName'          => 'goods',
            'P10_goodsDesc'         => 'goods',
            'P11_terminalType'      => 'IMEI',
            'P12_terminalId'        => $user->phone,
            'P13_orderIp'           => getIp(),
            'P14_period'            => 2,
            'P15_periodUnit'        => 'Day',
            'P16_serverCallbackUrl' => Yaf_Registry::get('config')->application->hostUrl . '/pay/helibaoPayNotify',
        ];
        $signStr = '&' . implode('&', $params);

        $params['sign'] = $this->MD5RSA_openssl($signStr);
        //$this->isValid($signStr, $this->MD5RSA_openssl($signStr));

        $params['sendValidateCode'] = "TRUE";
        $params['goodsQuantity']    = 1;
        $params['userAccount']      = $user->phone;
        $params['appType']          = 'H5';
        $params['appName']          = 'shuangte';
        $params['dealSceneType']    = 'QUICKPAY';
        $params['signatureType']    = 'MD5WITHRSA';
        $params['encryptionKey']    = $this->encryptionKey();

        Log::out('prepay $params:' . json_encode($params), 'helibao');
        $resStr = request($this->url_proc, $params);
        Log::out('prepay result:' . json_encode($resStr), 'helibao');
        $resArr = json_decode($resStr, true);

        if ($resArr['rt2_retCode'] === '0000') {
            $rows = [
                'member_id'    => $params['P4_userId'],
                'bind_id'      => $params['P3_bindId'],
                'order_sn'     => $input['order_sn'],
                'pay_order_no' => $params['P5_orderId'],
                'created_at'   => date('Y-m-d H:i:s')
            ];
            helibaoorderModel::insert($rows);
            ret(0, '绑卡支付预下单成功', ['pay_order_no' => $params['P5_orderId']]);
        } else {
            ret(30003, $resArr['rt3_retMsg']);
        }
    }

    // 支付短信
    public function BindPaySendValidateCodeAction()
    {
        $user = \app\service\auth::getUser();
        if ($user->status != 'normal') {
            throw new Exception("账号状态异常");
        }
        $payData = helibaoorderModel::query()
            ->where('bind_id', $this->get('bind_id'))
            ->where('pay_order_no', $this->get('pay_order_no'))
            ->first();
        if(!$payData){
            throw new Exception("预支付记录未找到");
        }
        $params  = [
            'P1_bizType'        => 'BindPaySendValidateCode',
            'P2_customerNumber' => $this->customerNo,
            'P3_orderId'        => $payData->pay_order_no,
            'P4_timestamp'      => date('YmdHis'),
        ];

        $signStr = '&' . implode('&', $params);

        $params['sign'] = $this->MD5RSA_openssl($signStr);
        //$this->isValid($signStr, $this->MD5RSA_openssl($signStr));

        $params['signatureType'] = 'MD5WITHRSA';
        $params['encryptionKey'] = $this->encryptionKey();

        Log::out('prepay sms $params:' . json_encode($params), 'helibao');
        $resStr = request($this->url_proc, $params);
        Log::out('prepay sms $resStr:' . $resStr, 'helibao');
        $resArr = json_decode($resStr, true);

        if ($resArr['rt2_retCode'] === '0000') {
            ret(0, '短信发送成功');
        } else {
            ret(30003, $resArr['rt3_retMsg']);
        }
    }

    // 绑卡支付
    public function ConfirmBindPayAction()
    {
        $user = \app\service\auth::getUser();
        if ($user->status != 'normal') {
            throw new Exception("账号状态异常");
        }
        $input = $this->postData;

        $payData = helibaoorderModel::query()
            ->where('bind_id', $this->get('bind_id'))
            ->where('pay_order_no', $this->get('pay_order_no'))
            ->first();
        if(!$payData){
            throw new Exception("预支付记录未找到");
        }

        $params  = [
            'P1_bizType'        => 'ConfirmBindPay',
            'P2_customerNumber' => $this->customerNo,
            'P3_orderId'        => $payData->pay_order_no,
            'P4_timestamp'      => date('YmdHis'),
            'P5_validateCode'   => $this->aes($input['smscode']),
        ];

        $signStr = '&' . implode('&', $params);
        //dump($signStr);

        $params['sign'] = $this->MD5RSA_openssl($signStr);
        //$this->isValid($signStr, $this->MD5RSA_openssl($signStr));

        $params['signatureType'] = 'MD5WITHRSA';
        $params['encryptionKey'] = $this->encryptionKey();

        Log::out('pay $params:' . json_encode($params), 'helibao');
        $resStr = request($this->url_proc, $params);
        Log::out('pay $resStr:' . json_encode($resStr), 'helibao');
        $resArr = json_decode($resStr, true);
        if ($resArr['rt2_retCode'] === '0000' && $resArr['rt9_orderStatus'] === 'SUCCESS') {
            $payData->transaction_no = $resArr['rt6_serialNumber'];
            $payData->save();
            ret(0, '绑卡支付成功');
        } else {
            ret(30003, $resArr['rt3_retMsg']);
        }
    }


    private function aes($str)
    {
        return openssl_encrypt($str, 'AES-128-ECB', $this->aesKey);
    }

    public function encryptionKey()
    {
        // 密钥
        //echo $this->RSA_openssl($this->aesKey);
        return file_get_contents("http://tsst22.xyckj178.top:8999/quickPay/quickPayApi/getEncode?data=" . $this->aesKey);
    }

    public function signAction()
    {
        $pkcs12 = file_get_contents($this->merchantCert);
        $res    = openssl_pkcs12_read($pkcs12, $certs, '123456');
        dump($res);
        dump($certs);
        $rsa_key = openssl_pkey_get_public($pkcs12);
        dump($rsa_key);
    }

    /**
     * RSA数据加密解密
     * @param type $data
     * @param type $mode encode加密  decode解密
     */
    private function RSA_openssl(string $data, string $mode = 'encode')
    {
        if (empty($data)) {
            throw new Exception('data参数不能为空');
        }

        $rsa_public = file_get_contents($this->publicKey);
        $rsa_key    = openssl_pkey_get_public($rsa_public);
        if (!$rsa_key) {
            throw new Exception('公钥不可用');
        }
        dump($rsa_key);

        $return_en = openssl_public_encrypt($this->noPaddingPad($data, mb_strlen($data)), $crypted, $rsa_key, OPENSSL_KEYTYPE_EC);
        dump("return_en:" . $return_en);
        dump("crypted:" . $crypted);
        if ($return_en) {
            throw new Exception('加密失败,请检查RSA秘钥');
        }
        return base64_encode($crypted);
    }

    /**
     * OPENSSL_NO_PADDING 补齐数据
     *
     * @param $str
     * @param $blocksize
     *
     * @return string
     */
    private function noPaddingPad($str, $blocksize)
    {
        $pad = $blocksize - (strlen($str) % $blocksize);
        return $str . str_repeat(chr($pad), $pad);
    }

    /**
     * OPENSSL_NO_PADDING 还原数据
     *
     * @param $text
     *
     * @return false|string
     */
    private function noPaddingUnpad($text)
    {
        $pad = ord($text[strlen($text) - 1]);
        if ($pad > strlen($text)) {
            return false;
        }
        return substr($text, 0, -1 * $pad);
    }


    private function MD5RSA_openssl(string $data, string $mode = 'encode')
    {
        if (empty($data)) {
            throw new Exception('data参数不能为空');
        }
        $cert = file_get_contents($this->merchantCert);

        $rsa_key = openssl_pkcs12_read($cert, $certs, '123456');
        if (!$rsa_key) {
            throw new Exception('私钥不可用');
        }
        $rsa_key = openssl_pkey_get_private($certs['pkey']);
        if (!$rsa_key) {
            throw new Exception('私钥不可用.');
        }

        $return_en = openssl_sign($data, $signature, $rsa_key, OPENSSL_ALGO_MD5);
        if (!$return_en) {
            throw new Exception('加密失败,请检查RSA秘钥');
        }

        return base64_encode($signature);
    }


    public function isValid($data = '', $signature = '')
    {
        if (empty($data) || empty($signature)) {
            throw new Exception('data和signature参数不能为空');
        }

        $cert    = file_get_contents($this->merchantCert);
        $rsa_key = openssl_pkcs12_read($cert, $certs, '123456');
        if (!$rsa_key) {
            throw new Exception('私钥不可用');
        }

        $ret = openssl_verify($data, base64_decode($signature), $certs['cert'], OPENSSL_ALGO_MD5);
        switch ($ret) {
            case -1:
                echo "error";
                break;
            default:
                echo $ret == 1 ? "correct" : "incorrect";//0:incorrect
                break;
        }
    }


}


class CertSerialUtil
{

    private static function bytesToInteger($bytes)
    {
        $val = 0;
        for ($i = 0; $i < count($bytes); $i++) {
// 			$val += (($bytes [$i] & 0xff) << (8 * (count ( $bytes ) - 1 - $i)));
            $val += $bytes [$i] * pow(256, count($bytes) - 1 - $i);
// 			echo $val . "<br>\n";
        }
        return $val;
    }

    private static function bytesToBigInteger($bytes)
    {
        $val = 0;
        for ($i = 0; $i < count($bytes); $i++) {
            $val = bcadd($val, bcmul($bytes [$i], bcpow(256, count($bytes) - 1 - $i)));
// 			echo $val . "<br>\n";
        }
        return $val;
    }

    private static function toStr($bytes)
    {
        $str = '';
        foreach ($bytes as $ch) {
            $str .= chr($ch);
        }
        return $str;
    }

    public static function getSerial($fileData, &$errMsg)
    {

// 		$fileData = str_replace('\n','',$fileData);
// 		$fileData = str_replace('\r','',$fileData);

        $start = "-----BEGIN CERTIFICATE-----";
        $end   = "-----END CERTIFICATE-----";
        $data  = trim($fileData);
        if (substr($data, 0, strlen($start)) != $start ||
            substr($data, strlen($data) - strlen($end)) != $end) {
            // echo $fileData;
            $errMsg = "error pem data";
            return false;
        }

        $data    = substr($data, strlen($start), strlen($data) - strlen($end) - strlen($start));
        $bindata = base64_decode($data);
        $bindata = unpack('C*', $bindata);

        $byte = array_shift($bindata);
        if ($byte != 0x30) {
            $errMsg = "1st tag " . $byte . " is not 30";
            return false;
        }

        $length = CertSerialUtil::readLength($bindata);
        $byte   = array_shift($bindata);
        if ($byte != 0x30) {
            $errMsg = "2nd tag " . $byte . " is not 30";
            return false;
        }

        $length = CertSerialUtil::readLength($bindata);
        $byte   = array_shift($bindata);
// 		echo $byte . "<br>\n";
        if ($byte == 0xa0) { //version tag.
            $length = CertSerialUtil::readLength($bindata);
            CertSerialUtil::readData($bindata, $length);
            $byte = array_shift($bindata);
        }

// 		echo $byte . "<br>\n";
        if ($byte != 0x02) { //x509v1 has no version tag, x509v3 has.
            $errMsg = "4th/3rd tag " . $byte . " is not 02";
            return false;
        }
        $length = CertSerialUtil::readLength($bindata);
        $serial = CertSerialUtil::readData($bindata, $length);
// 		echo bin2hex(CertSerialUtil::toStr( $serial ));
        return CertSerialUtil::bytesToBigInteger($serial);
    }

    private static function readLength(&$bindata)
    {
        $byte = array_shift($bindata);
        if ($byte < 0x80) {
            $length = $byte;
        } else {
            $lenOfLength = $byte - 0x80;
            for ($i = 0; $i < $lenOfLength; $i++) {
                $lenBytes [] = array_shift($bindata);
            }
            $length = CertSerialUtil::bytesToInteger($lenBytes);
        }
        return $length;
    }

    private static function readData(&$bindata, $length)
    {
        $data = array();
        for ($i = 0; $i < $length; $i++) {
            $data [] = array_shift($bindata);
        }
        return $data;
    }
}